#ifndef STX_PLATFORM_POSIX_THREAD_MUTEX_HPP
#define STX_PLATFORM_POSIX_THREAD_MUTEX_HPP

#include <stx/platform/posix/thread/common.hpp>
#include <stx/platform/posix/thread/mutex_attributes.hpp>

namespace stx {

namespace posix {

class mutex: noncopyable
{
private:
    
    void construct(const mutex_attributes& attr)
    {
        int ret = pthread_mutex_init(&handle_, attr.handle());
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_init");
        }
    }
    
public:
    
    typedef mutex this_type;
    
    mutex()
    {
        int ret = pthread_mutex_init(&handle_, NULL);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_init");
        }
    }
    
    mutex(const mutex_attributes& attr)
    {
        construct(attr);
    }
    
    ~mutex()
    {
        int ret = pthread_mutex_destroy(&handle_);
        if (ret) {
            STX_DESTRUCTOR_SYSTEM_ERROR_VAL(ret, "pthread_mutex_destroy");
        }
    }
    
    #if STX_HAS_PTHREAD_MUTEX_GETPRIOCEILING
    
    int priority_ceiling() const
    {
        int val;
        int ret = pthread_mutex_getprioceiling(&handle_, &val);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_getprioceiling");
        }
        return val;
    }
    
    #endif
    
    #if STX_HAS_PTHREAD_MUTEX_SETPRIOCEILING
    
    this_type& set_priority_ceiling(int val)
    {
        int old_val;
        return set_priority_ceiling(val, old_val);
    }
    
    this_type& set_priority_ceiling(int val, int& old_val)
    {
        int ret = pthread_mutex_setprioceiling(&handle_, val, &old_val);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_setprioceiling");
        }
        return *this;
    }
    
    #endif
    
    void lock()
    {
        int ret = pthread_mutex_lock(&handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_lock");
        }
    }
    
    bool try_lock()
    {
        int ret = pthread_mutex_trylock(&handle_);
        if (ret == EBUSY) {
            return false;
        } else  {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_trylock");
        }
        return true;
    }
    
    bool timed_lock(const struct timespec& timeout)
    {
        int ret = pthread_mutex_timedlock(&handle_, &timeout);
        if (ret == ETIMEDOUT) {
            return false;
        } else {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_timedlock");
        }
        return true;
    }
    
    void unlock()
    {
        int ret = pthread_mutex_unlock(&handle_);
        if (ret) {
            STX_THROW_SYSTEM_ERROR_VAL(ret, "pthread_mutex_unlock");
        }
    }
    
    typedef pthread_mutex_t handle_type;
    
    handle_type* handle()
    {
        return &handle_;
    }
    
    const handle_type* handle() const
    {
        return &handle_;
    }
    
private:
    
    handle_type handle_;
};

} //namespace posix

} // namespace stx

#endif // STX_PLATFORM_POSIX_THREAD_MUTEX_HPP
